import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
from tqdm import tqdm
import random
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 爬取新浪财经历史分红数据
def scrape_sina_dividend_data(start_page=1, end_page=100):
    """爬取新浪财经历史分红数据第1-100页的内容"""
    # 初始化数据列表
    all_data = []
    
    # 遍历每一页
    for page in tqdm(range(start_page, end_page + 1), desc="爬取进度"):
        # 每次迭代开始时重新创建headers，确保它始终是正确的字典类型
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Referer': 'https://vip.stock.finance.sina.com.cn/',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
        }
        try:
            logger.info(f"开始爬取第{page}页数据")
            
            # 构建URL - 尝试多种可能的分红数据页面
            # 新浪财经的不同页面可能使用不同的分页参数格式
            urls_to_try = [
                # URL1: 股息率排行页面 - 使用p参数
                f'https://vip.stock.finance.sina.com.cn/q/go.php/vFinanceAnalyze/kind/dpgxl/index.phtml?p={page}',
                # URL2: 分红派息页面 - 使用page参数
                f'https://vip.stock.finance.sina.com.cn/corp/view/vCB_AllDividendHistory.php?page={page}',
                # URL3: 新浪财经市场页的分红板块
                f'https://vip.stock.finance.sina.com.cn/mkt/data/分红派息?p={page}',
                # URL4: 股票分红数据页
                f'https://vip.stock.finance.sina.com.cn/corp/go.php/vFinanceAnalyze/kind/fhps/index.phtml?p={page}'
            ]
            
            response = None
            # 尝试多个URL直到成功
            for url in urls_to_try:
                try:
                    logger.debug(f"尝试访问URL: {url}")
                    # 确保headers是字典类型
                    if not isinstance(headers, dict):
                        logger.error(f"第{page}页: 请求头类型错误，应为字典，实际为{type(headers)}")
                        # 重新创建请求头
                        headers = {
                            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                            'Referer': 'https://vip.stock.finance.sina.com.cn/mkt/',
                            'Accept-Language': 'zh-CN,zh;q=0.9',
                            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
                        }
                    response = requests.get(url, headers=headers, timeout=15)
                    
                    # 检查响应状态
                    if response.status_code == 200:
                        logger.info(f"成功访问第{page}页: {url}")
                        break
                    else:
                        logger.warning(f"第{page}页请求失败，状态码: {response.status_code}，URL: {url}")
                except requests.RequestException as e:
                    logger.warning(f"访问URL {url} 时出错: {str(e)}")
                    continue
            
            # 如果所有URL都失败，跳过此页
            if not response or response.status_code != 200:
                logger.error(f"第{page}页所有URL都请求失败")
                continue
            
            # 设置正确的编码
            try:
                response.encoding = 'gb2312'  # 新浪财经通常使用gb2312编码
            except:
                response.encoding = response.apparent_encoding
            
            # 检查响应内容是否为空
            if len(response.text.strip()) == 0:
                logger.warning(f"第{page}页响应内容为空")
                continue
            
            # 解析HTML
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 查找表格 - 尝试多种方法查找可能包含分红数据的表格
            table = None
            
            # 方法1: 尝试查找特定class的表格
            possible_classes = ['list_table', 'tbl_data', 'tablelist', 'datalist']
            for class_name in possible_classes:
                table = soup.find('table', class_=class_name)
                if table:
                    logger.info(f"第{page}页: 找到class为{class_name}的表格")
                    break
            
            # 方法2: 如果没找到，尝试查找包含特定关键词的表格
            if not table:
                tables = soup.find_all('table')
                keyword_sets = [
                    ['代码', '名称'],  # 代码和名称通常一起出现
                    ['分红', '派息'],  # 分红相关关键词
                    ['股息率', '收益率']  # 股息率相关关键词
                ]
                
                for t in tables:
                    # 提取表格中的所有文本
                    table_text = t.get_text()
                    # 检查是否包含任何一组关键词
                    for keywords in keyword_sets:
                        if all(keyword in table_text for keyword in keywords):
                            table = t
                            logger.info(f"第{page}页: 找到包含关键词{keywords}的表格")
                            break
                    if table:
                        break
            
            # 如果找到表格，提取数据
            if table:
                try:
                    # 获取表头
                    header_cells = table.find_all('th')
                    if not header_cells or len(header_cells) < 2:
                        # 尝试查找第一行的td作为表头
                        first_row = table.find('tr')
                        if first_row:
                            header_cells = first_row.find_all('td')
                    
                    if header_cells:
                        headers_list = [cell.text.strip() for cell in header_cells]
                        
                        # 获取数据行 - 跳过表头行
                        data_rows = table.find_all('tr')[1:]
                        
                        for row in data_rows:
                            try:
                                cells = row.find_all('td')
                                if len(cells) >= len(headers_list):
                                    # 提取单元格文本
                                    row_data = [cell.text.strip() for cell in cells[:len(headers_list)]]
                                    
                                    # 检查数据是否有效
                                    if any(row_data) and len([d for d in row_data if d]) > 2:
                                        # 创建数据字典并添加到列表
                                        try:
                                            data_dict = dict(zip(headers_list, row_data))
                                            all_data.append(data_dict)
                                        except Exception as e:
                                            logger.error(f"第{page}页: 构建数据字典时出错: {str(e)}")
                            except Exception as e:
                                logger.error(f"第{page}页: 处理数据行时出错: {str(e)}")
                        
                        logger.info(f"第{page}页成功提取到{len(data_rows)}条数据")
                    else:
                        logger.warning(f"第{page}页: 表格没有找到有效的表头")
                except Exception as e:
                    logger.error(f"第{page}页: 提取表格数据时出错: {str(e)}")
            else:
                logger.warning(f"第{page}页: 未找到包含分红数据的表格")
                # 打印部分HTML内容用于调试（仅当DEBUG级别时）
                if logger.isEnabledFor(logging.DEBUG) and len(response.text) > 500:
                    logger.debug(f"页面内容预览: {response.text[:500]}...")
            
            # 智能休眠 - 根据页面是否成功获取数据调整休眠时间
            if table and len(all_data) > len(all_data) - 5:  # 如果成功获取了数据
                sleep_time = random.uniform(2, 4)  # 成功时休眠时间稍长
            else:
                sleep_time = random.uniform(1, 2)  # 失败时休眠时间稍短
            
            logger.debug(f"第{page}页: 休眠{sleep_time:.2f}秒")
            time.sleep(sleep_time)
            
        except requests.RequestException as e:
            logger.error(f"第{page}页: 请求异常: {str(e)}")
            # 增加重试逻辑
            retry_count = 0
            max_retries = 2
            while retry_count < max_retries:
                try:
                    logger.info(f"第{page}页: 尝试重试第{retry_count+1}次")
                    time.sleep(5)  # 重试前等待更长时间
                    # 重新尝试最后一个有效的URL
                    if response and hasattr(response, 'url'):
                        response = requests.get(response.url, headers=headers, timeout=20)
                        response.encoding = 'gb2312'
                        if response.status_code == 200:
                            # 重新解析
                            soup = BeautifulSoup(response.text, 'html.parser')
                    # 这里可以重新调用表格查找和数据提取的逻辑，但为了简化，我们直接跳过
                        break
                except Exception as retry_e:
                    logger.error(f"第{page}页: 重试失败: {str(retry_e)}")
                retry_count += 1
            continue
        except AttributeError as e:
            logger.error(f"第{page}页: 属性错误 - 可能是页面结构变化: {str(e)}")
            # 这个错误可能是因为BeautifulSoup解析结果不符合预期
            if response is not None:
                logger.debug(f"响应内容类型: {type(response.text)}")
            else:
                logger.debug("响应对象为None")
            continue
        except Exception as e:
            logger.error(f"第{page}页: 其他未知错误: {str(e)}")
            # 打印完整的错误栈以帮助调试
            import traceback
            logger.debug(traceback.format_exc())
            continue
    
    return pd.DataFrame(all_data)

# 导入外部数据预处理模块
try:
    from data_preprocessor import load_and_check_data, show_data_preview, process_listing_date, normalize_numeric_variables, save_preprocessed_data
    external_preprocessor_available = True
except ImportError as e:
    print(f"无法导入外部预处理模块: {str(e)}")
    print("将使用内置的预处理函数")
    external_preprocessor_available = False

# 内置数据预处理函数
def preprocess_data(df):
    """对爬取的数据进行预处理"""
    print("原始数据形状:", df.shape)
    
    # 显示所有列名
    print("数据列名:", df.columns.tolist())
    
    # 展示数据前5行
    print("数据前5行:")
    print(df.head())
    
    # 检查代码和名称相关列的重复值
    code_cols = [col for col in df.columns if any(keyword in col for keyword in ['代码', 'code', '股票代码'])]
    name_cols = [col for col in df.columns if any(keyword in col for keyword in ['名称', 'name', '股票名称'])]
    
    if code_cols:
        code_duplicates = df[code_cols[0]].duplicated().sum()
        print(f"{code_cols[0]}列重复值数量: {code_duplicates}")
    
    if name_cols:
        name_duplicates = df[name_cols[0]].duplicated().sum()
        print(f"{name_cols[0]}列重复值数量: {name_duplicates}")
    
    # 对日期相关列进行处理，提取年、月、日变量
    date_cols = [col for col in df.columns if any(keyword in col for keyword in ['日期', '上市', 'time'])]
    
    for date_col in date_cols:
        try:
            # 确保日期列是字符串类型
            df[date_col] = df[date_col].astype(str)
            
            # 尝试从不同格式的日期中提取年月日
            if df[date_col].str.match(r'^\d{8}$').any():
                # 处理格式为YYYYMMDD的日期
                df[date_col + '_年'] = df[date_col].str[:4]
                df[date_col + '_月'] = df[date_col].str[4:6]
                df[date_col + '_日'] = df[date_col].str[6:8]
            elif df[date_col].str.match(r'^\d{4}-\d{2}-\d{2}$').any():
                # 处理格式为YYYY-MM-DD的日期
                df[date_col + '_年'] = df[date_col].str[:4]
                df[date_col + '_月'] = df[date_col].str[5:7]
                df[date_col + '_日'] = df[date_col].str[8:10]
        except Exception as e:
            print(f"处理{date_col}列时出错: {str(e)}")
    
    # 将数字列转换为数值类型
    numeric_keywords = ['股息率', '分红', '融资', '金额', '比例', 'rate', 'dividend', 'finance']
    
    for col in df.columns:
        if any(keyword in col for keyword in numeric_keywords):
            try:
                # 处理百分比和其他特殊字符
                df[col] = pd.to_numeric(df[col].str.replace('%', '').str.replace('--', '0'), errors='coerce')
            except Exception:
                # 如果无法转换，跳过该列
                pass
    
    return df

# 主函数
def main():
    print("开始爬取新浪财经历史分红数据...")
    
    # 爬取1-100页数据
    df = scrape_sina_dividend_data(1, 100)
    
    if not df.empty:
        # 保存原始数据到当前目录
        output_file = "新浪财经历史分红数据.xlsx"
        df.to_excel(output_file, index=False)
        print(f"原始数据已保存至: {output_file}")
        
        # 预处理数据
        print("\n开始预处理数据...")
        
        if external_preprocessor_available:
            # 使用外部预处理模块
            print("使用外部预处理模块进行数据处理")
            # 由于外部预处理模块需要从文件读取数据，我们先保存文件，然后让外部模块读取
            from data_preprocessor import main as preprocessor_main
            preprocessor_main()
        else:
            # 使用内置预处理函数
            processed_df = preprocess_data(df)
            
            # 保存预处理后的数据到当前目录
            processed_output_file = "新浪财经历史分红数据_预处理.xlsx"
            processed_df.to_excel(processed_output_file, index=False)
            print(f"预处理后的数据已保存至: {processed_output_file}")
    else:
        print("未爬取到任何数据！")

if __name__ == "__main__":
    main()